/*
 * Copyright (C) Groupe Citypassenger Inc. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU Affero General Public License in all respects
 * for all of the code used other than OpenSSL.  
 */


#include "config.h"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif 

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif 

#include "monit.h"
#include "event.h"
#include "process.h"
#include "file_list_loader.h"

/**
 *  JSON routines for status and event notification message handling.
 *
 *  @file
 */


/* ----------------------------------------------------------------- Private */

static void add_with_escape(StringBuffer_T B, const char * d, size_t buf_len) {
        for (int i = 0; i < buf_len; i++) {
                if (d[i] == '"')
                        StringBuffer_append(B, "\\\"");
                else if (d[i] == '\\')
                        StringBuffer_append(B, "\\\\");
                else if (d[i] == '\b')
                        StringBuffer_append(B, "\\b");
                else if (d[i] == '\f')
                        StringBuffer_append(B, "\\f");
                else if (d[i] == '\n')
                        StringBuffer_append(B, "\\n");
                else if (d[i] == '\r')
                        StringBuffer_append(B, "\\r");
                else if (d[i] == '\t')
                        StringBuffer_append(B, "\\t");
                else
                        StringBuffer_append(B, "%c", d[i]);
        }
}


/**
 * Prints a document header into the given buffer.
 * @param B StringBuffer object
 * @param V Format version
 * @param myip The client-side IP address
 */
static void document_head(StringBuffer_T B, int V, const char *myip) {
        if (V == 2)
                StringBuffer_append(B, "{ \"monit\" : { \"id\":\"%s\", \"incarnation\":%lld, \"version\":\"%s\", \"server\" : {", Run.id, (long long)Run.incarnation, VERSION);
        else
                StringBuffer_append(B,
                        "{ \"monit\" : { \"server\" : {"
                        "\"id\":\"%s\", \"incarnation\":%lld, \"version\":\"%s\", ",
                        Run.id,
                        (long long)Run.incarnation,
                        VERSION);
        char* system_name = Run.system->name ? Run.system->name : "";
        StringBuffer_T SN = StringBuffer_create(64);
        add_with_escape(SN, system_name, strlen(system_name));

        StringBuffer_append(B,
                "\"uptime\" : %ld, "
                "\"poll\" : %d, "
                "\"startdelay\" : %d, "
                "\"localhostname\" : \"%s\", "
                "\"controlfile\" : \"%s\", ",
                (long)Util_getProcessUptime(Run.pidfile),
                Run.polltime,
                Run.startdelay,
                StringBuffer_toString(SN),
                Run.controlfile ? Run.controlfile : "");
        StringBuffer_free(&SN);
        if (Run.dohttpd) {
                StringBuffer_append(B, "\"httpd\":{\"address\":\"%s\", \"port\":%d, \"ssl\":%d",
                Run.bind_addr ? Run.bind_addr : myip, Run.httpdport, Run.httpdssl);

                if (Run.mmonitcredentials)
                        StringBuffer_append(B, ",\"credentials\":{\"username\":\"%s\",\"password\":\"%s\"}}",
                                              Run.mmonitcredentials->uname, Run.mmonitcredentials->passwd);
                else 
                        StringBuffer_append(B, "}");
        }

        StringBuffer_append(B,
                "}, \"platform\" : {"
                "\"name\":\"%s\","
                "\"release\":\"%s\","
                "\"version\":\"%s\","
                "\"machine\":\"%s\","
                "\"cpu\":%d,"
                "\"memory\":%lu,"
                "\"swap\":%lu"
                "},",
                systeminfo.uname.sysname,
                systeminfo.uname.release,
                systeminfo.uname.version,
                systeminfo.uname.machine,
                systeminfo.cpus,
                systeminfo.mem_kbyte_max,
                systeminfo.swap_kbyte_max);
}

/**
 * Read file and write content as json format
 * return 0 if success, 1 file not exist,  2 if error
 */
static int write_report_file(StringBuffer_T B, Report_T r) {
        struct stat file_stat;
        if (stat(r->path, &file_stat) == -1) {
                LogInfo("%s: %s does not exists\n", prog, r->path);
                return 1;
        }

        if (!S_ISREG(file_stat.st_mode)) {
                LogError("%s: requested file is not regular %s\n", prog, r->path);
                return 2;
        }

        FILE* f = fopen(r->path, "r");
        if (NULL == f) {
                LogCritical("%s: failed to fopen requested file %s\n", prog, r->path);
                return 2;
        }

        char *file_data = malloc(4096);
        if (file_data == NULL) {
                LogError("%s: malloc failed in write_report_file %s\n", prog, r->path);
                fclose(f);
                return 2;
        }

        StringBuffer_append(B, "\"%s\":\"", r->path);
        while (!feof(f)) {
                size_t readed = fread(file_data, 1, 4096, f);
                add_with_escape(B, file_data, readed);
        }
        StringBuffer_append(B, "\",");

        free(file_data);
        fclose(f);
        return 0;
}

/**
 * Prints a service status into the given buffer.
 * @param S Service object
 * @param B StringBuffer object
 * @param L Status information level
 * @param V Format version
 */
static void status_service(Service_T S, StringBuffer_T B, short L, int V) {

        if ( V == 2 ) {
                char* system_name = S->name ? S->name : "unknown";
                StringBuffer_T SN = StringBuffer_create(64);
                add_with_escape(SN, system_name, strlen(system_name));
                StringBuffer_append(B, "\"%s\":{\"name\":\"%s\", \"type\":%d,",
                                        StringBuffer_toString(SN),
                                        StringBuffer_toString(SN),
                                        S->type);
                StringBuffer_free(&SN);

        } else {
                char* system_name = S->name ? S->name : "";
                StringBuffer_T SN = StringBuffer_create(64);
                add_with_escape(SN, system_name, strlen(system_name));
                StringBuffer_append(B, "{\"name\":\"%s\", \"type\":%d,",
                                        StringBuffer_toString(SN), S->type);
                StringBuffer_free(&SN);

        }

        StringBuffer_append(B,
                "\"collected_sec\":%ld,"
                "\"collected_usec\":%ld,"
                "\"status\":%d,"
                "\"status_hint\":%d,"
                "\"monitor\":%d,"
                "\"monitormode\":%d,"
                "\"pendingaction\":%d",
                S->collected.tv_sec,
                (long)S->collected.tv_usec,
                S->error,
                S->error_hint,
                S->monitor,
                S->mode,
                S->doaction);
        if (S->every.type != EVERY_CYCLE) {
                StringBuffer_append(B, ",\"every\":{ \"type\":%d,", S->every.type);
                if (S->every.type == 1)
                        StringBuffer_append(B, "\"counter\":%d,\"number\":%d",
                        S->every.spec.cycle.counter, S->every.spec.cycle.number);
                else
                        StringBuffer_append(B, "\"cron\":\"%s\"", S->every.spec.cron);
                StringBuffer_append(B, "}");
        }

        if (L == LEVEL_FULL) {
                if (Util_hasServiceStatus(S)) {
                        if (S->type == TYPE_FILE || S->type == TYPE_DIRECTORY || S->type == TYPE_FIFO || S->type == TYPE_FILESYSTEM)
                                StringBuffer_append(B, ",\"mode\":\"%o\",\"uid\":%d,\"gid\":%d",
                                S->inf->st_mode & 07777, (int)S->inf->st_uid, (int)S->inf->st_gid);
                        if (S->type == TYPE_FILE || S->type == TYPE_FIFO || S->type == TYPE_DIRECTORY)
                                StringBuffer_append(B, ",\"timestamp\":%ld", (long)S->inf->timestamp);
                        if (S->type == TYPE_FILE) {
                                StringBuffer_append(B, ",\"size\":%llu", (unsigned long long) S->inf->priv.file.st_size);
                                if (S->checksum)
                                        StringBuffer_append(B, ",\"checksum\": { \"type\":\"%s\", \"sum\" : \"%s\"}",
                                        checksumnames[S->checksum->type], S->inf->priv.file.cs_sum);
                        }
                        if (S->type == TYPE_FILESYSTEM) {
                                StringBuffer_append(B,
                                        ",\"flags\":%d,"
                                        "\"block\":{"
                                        "\"percent\":\"%.1f\","
                                        "\"usage\":\"%.1f\","
                                        "\"total\":\"%.1f\""
                                        "}",
                                        S->inf->priv.filesystem.flags,
                                        S->inf->priv.filesystem.space_percent/10.,
                                        S->inf->priv.filesystem.f_bsize > 0 ? (float)S->inf->priv.filesystem.space_total / (float)1048576 * (float)S->inf->priv.filesystem.f_bsize : 0,
                                        S->inf->priv.filesystem.f_bsize > 0 ? (float)S->inf->priv.filesystem.f_blocks / (float)1048576 * (float)S->inf->priv.filesystem.f_bsize : 0);
                                if (S->inf->priv.filesystem.f_files > 0) {
                                        StringBuffer_append(B,
                                                ",\"inode\":{"
                                                "\"percent\":\"%.1f\","
                                                "\"usage\":%ld,"
                                                "\"total\":%ld"
                                                "}",
                                                S->inf->priv.filesystem.inode_percent/10.,
                                                S->inf->priv.filesystem.inode_total,
                                                S->inf->priv.filesystem.f_files);
                                }
                        }
                        if (S->type == TYPE_PROCESS) {
                                StringBuffer_append(B,
                                        ",\"pid\":%d,"
                                        "\"ppid\":%d,"
                                        "\"uptime\":%ld",
                                        S->inf->priv.process.pid,
                                        S->inf->priv.process.ppid,
                                        (long)S->inf->priv.process.uptime);
                                if (Run.doprocess) {
                                        StringBuffer_append(B,
                                                ",\"children\":%d,"
                                                "\"memory\":{"
                                                "\"percent\":\"%.1f\","
                                                "\"percenttotal\":\"%.1f\","
                                                "\"kilobyte\":\"%ld\","
                                                "\"kilobytetotal\":\"%ld\""
                                                "},\"cpu\":{"
                                                "\"percent\":\"%.1f\","
                                                "\"percenttotal\":\"%.1f\""
                                                "}",
                                                S->inf->priv.process.children,
                                                S->inf->priv.process.mem_percent/10.0,
                                                S->inf->priv.process.total_mem_percent/10.0,
                                                S->inf->priv.process.mem_kbyte,
                                                S->inf->priv.process.total_mem_kbyte,
                                                S->inf->priv.process.cpu_percent/10.0,
                                                S->inf->priv.process.total_cpu_percent/10.0);
                                }
                        }
                        if (S->type == TYPE_HOST && S->icmplist) {
                                for (Icmp_T i = S->icmplist; i; i = i->next) {
                                        StringBuffer_append(B,
                                                ",\"icmp\":{"
                                                "\"type\":\"%s\","
                                                "\"responsetime\":\"%.3f\""
                                                "}",
                                                icmpnames[i->type],
                                                i->is_available ? i->response : -1.);
                                }
                        }
                        if ((S->type == TYPE_HOST || S->type == TYPE_PROCESS) && S-> portlist) {
                                StringBuffer_append(B, ",\"ports\":{");
                                Port_T p;
                                int chop = 0;
                                for (p = S->portlist; p; p = p->next) {
                                        if (p->family == AF_INET) {
                                                StringBuffer_append(B,
                                                    "\"%d\":{"
                                                    "\"hostname\":\"%s\","
                                                    "\"portnumber\":%d,"
                                                    "\"request\":\"%s\","
                                                    "\"protocol\":\"%s\","
                                                    "\"type\":\"%s\","
                                                    "\"responsetime\":%.3f"
                                                    "},",
                                                    p->port,
                                                    p->hostname?p->hostname:"",
                                                    p->port,
                                                    p->request?p->request:"",
                                                    p->protocol->name?p->protocol->name:"",
                                                    Util_portTypeDescription(p),
                                                    p->is_available?p->response:-1.);
                                                    chop = 1;
                                        } else if (p->family == AF_UNIX) {
                                                StringBuffer_append(B,
                                                    "\"%s\":{"
                                                    "\"path\":\"%s\","
                                                    "\"type\":\"unix\","
                                                    "\"protocol\":\"%s\","
                                                    "\"responsetime\":%.3f"
                                                    "}",
                                                    p->pathname?p->pathname:"",
                                                    p->pathname?p->pathname:"",
                                                    p->protocol->name?p->protocol->name:"",
                                                    p->is_available?p->response:-1.);
                                                    chop = 1;
                                        }
                                }
                                if (chop) StringBuffer_chop(B);
                                StringBuffer_append(B, "}");
                        }
                        if (S->type == TYPE_SYSTEM && Run.doprocess) {
                                StringBuffer_append(B,
                                        ",\"system\" : {"
                                        "\"load\" : {"
                                        "\"avg01\":%.2f,"
                                        "\"avg05\":%.2f,"
                                        "\"avg15\":%.2f"
                                        "}, \"cpu\" : {"
                                        "\"user\":%.1f,"
                                        "\"system\":%.1f"
#ifdef HAVE_CPU_WAIT
                                        ",\"wait\":%.1f"
#endif
                                        "},"
                                        "\"memory\" : {"
                                        "\"percent\" : %.1f,"
                                        "\"kilobyte\" : %ld"
                                        "},\"swap\" : {"
                                        "\"percent\" : %.1f,"
                                        "\"kilobyte\" : %ld"
                                        "}"
                                        "}",
                                        systeminfo.loadavg[0],
                                        systeminfo.loadavg[1],
                                        systeminfo.loadavg[2],
                                        systeminfo.total_cpu_user_percent > 0 ? systeminfo.total_cpu_user_percent/10. : 0,
                                        systeminfo.total_cpu_syst_percent > 0 ? systeminfo.total_cpu_syst_percent/10. : 0,
#ifdef HAVE_CPU_WAIT
                                        systeminfo.total_cpu_wait_percent > 0 ? systeminfo.total_cpu_wait_percent/10. : 0,
#endif
                                        systeminfo.total_mem_percent/10.,
                                        systeminfo.total_mem_kbyte,
                                        systeminfo.total_swap_percent/10.,
                                        systeminfo.total_swap_kbyte);
                        }
                        if (S->type == TYPE_PROGRAM && S->program->started) {
                                StringBuffer_append(B,
                                        ",\"program\": {"
                                        "\"started\":%lu,"
                                        "\"status\":%d"
                                        "}",
                                        (unsigned long)S->program->started,
                                        S->program->exitStatus);
                        }
                        if (S->configlist) {
                                Config_T c;
                                StringBuffer_append(B,",\"configurations\":{");
                                int chop = 0;
                                for(c= S->configlist; c; c= c->next) {
                                        if(c->path != NULL) { //this was asserted , but well, better safe than sorry file:p.y, line:2287
                                                struct file_list_entry* pfle = NULL;
                                                if (NULL == get_conf_content(c->path, &pfle)) continue;
                                                chop = 1;
                                                StringBuffer_append(B,
                                                        "\"%s\":\"", c->path);
                                                add_with_escape(B,pfle->map,pfle->size);
                                                StringBuffer_append(B,"\",");
                                        
                                        }
                                }
                                if (chop) StringBuffer_chop(B);
                                StringBuffer_append(B,"}");
                        }
                        if (S->reportlist) {
                                Report_T r;
                                StringBuffer_append(B,",\"reporting\":{");
                                int chop = 0;
                                for(r= S->reportlist; r; r= r->next) {
                                        if(r->path != NULL) { //this was asserted , but well, better safe than sorry file:p.y, line:2305
                                                chop = 1;
                                                int ret = write_report_file(B, r);
                                                if (ret == 1) {
                                                        StringBuffer_append(B, "\"%s\":null,", r->path);
                                                } else if (ret == 2) {
                                                        StringBuffer_append(B, "\"%s\":false,", r->path);
                                                }
                                        }
                                }
                                if (chop) StringBuffer_chop(B);
                                StringBuffer_append(B,"}");
                        }
                }
        }
        StringBuffer_append(B, "},");
}


/**
 * Prints a servicegroups into the given buffer.
 * @param SG ServiceGroup object
 * @param B StringBuffer object
 * @param L Status information level
 */
static void status_servicegroup(ServiceGroup_T SG, StringBuffer_T B, short L) {
        StringBuffer_append(B, "\"%s\": [", SG->name);
        for (ServiceGroupMember_T SGM = SG->members; SGM; SGM = SGM->next)
                StringBuffer_append(B, "\"%s\",", SGM->name);
        if (SG->members) StringBuffer_chop(B);
        StringBuffer_append(B, "],");
}


/**
 * Prints a event description into the given buffer.
 * @param E Event object
 * @param B StringBuffer object
 */
static void status_event(Event_T E, StringBuffer_T B) {
        struct timeval *tv = Event_get_collected(E);
        StringBuffer_append(B,
                ",\"event\": {"
                "\"collected_sec\":%ld,"
                "\"collected_usec\":%ld,"
                "\"service\":\"%s\","
                "\"type\":%d,"
                "\"id\":%ld,"
                "\"state\":%d,"
                "\"action\":%d,"
                "\"message\":\"",
                tv->tv_sec,
                (long)tv->tv_usec,
                Event_get_id(E) == Event_Instance ? "Monit" : Event_get_source_name(E),
                Event_get_source_type(E),
                Event_get_id(E),
                Event_get_state(E),
                Event_get_action(E));
        // Escape the \"
        const char *msg = Event_get_message(E);
        add_with_escape(B,msg, strlen(msg));
        StringBuffer_append(B, "\"");
        Service_T s = Event_get_source(E);
        if (s && s->token)
                StringBuffer_append(B, ",\"token\":\"%s\"", s->token);
        StringBuffer_append(B, "}");
}


/* ------------------------------------------------------------------ Public */


/**
 * Get a XML formated message for event notification or general status
 * of monitored services and resources.
 * @param E An event object or NULL for general status
 * @param L Status information level
 * @param V Format version
 * @param myip The client-side IP address
 */
void status_json(StringBuffer_T B, Event_T E, short L, int V, const char *myip) {
        Service_T S;
        ServiceGroup_T SG;

        document_head(B, V, myip);
        if (V == 2)
                StringBuffer_append(B, "\"services\" : {");
        else
                StringBuffer_append(B, "\"services\" : [");
        for (S = servicelist_conf; S; S = S->next_conf)
                status_service(S, B, L, V);
        if (servicelist_conf) StringBuffer_chop(B);
        StringBuffer_append(B, (V == 2) ?  "}" : "]");
        if (V == 2) {
                StringBuffer_append(B, ",\"servicegroups\" : {");
                for (SG = servicegrouplist; SG; SG = SG->next)
                        status_servicegroup(SG, B, L);
                if (servicegrouplist) StringBuffer_chop(B);
                StringBuffer_append(B, "}");
        }
        if (E)
                status_event(E, B);
        StringBuffer_append(B, "}}");
}

